bitkeeper revision 1.1108.1.17 (410130f82d--8rFf_j0nL2KMhoKYvg)
authormjw@wray-m-3.hpl.hp.com <mjw@wray-m-3.hpl.hp.com>
Fri, 23 Jul 2004 15:38:32 +0000 (15:38 +0000)
committermjw@wray-m-3.hpl.hp.com <mjw@wray-m-3.hpl.hp.com>
Fri, 23 Jul 2004 15:38:32 +0000 (15:38 +0000)
Make rebooting a domain use the same domain id and console,
without disconnecting any connected console.

tools/python/xen/xend/XendConsole.py
tools/python/xen/xend/XendDomain.py
tools/python/xen/xend/XendDomainInfo.py
tools/python/xen/xend/server/SrvConsole.py
tools/python/xen/xend/server/SrvConsoleDir.py
tools/python/xen/xend/server/SrvDaemon.py
tools/python/xen/xend/server/SrvDomain.py
tools/python/xen/xend/server/console.py
tools/python/xen/xend/server/controller.py
tools/python/xen/xm/create.py

index 9cc57ff45668f716a219997d4d6e0aa67c25a013..6825dc5baab73269f23e19649c468022c63ca7d8 100644 (file)
@@ -8,6 +8,7 @@ import sxp
 import XendRoot
 xroot = XendRoot.instance()
 import XendDB
+from XendError import XendError
 
 import EventServer
 eserver = EventServer.instance()
@@ -15,160 +16,38 @@ eserver = EventServer.instance()
 from xen.xend.server import SrvDaemon
 daemon = SrvDaemon.instance()
 
-class XendConsoleInfo:
-    """Console information record.
-    """
-
-    def __init__(self, console, dom1, port1, dom2, port2, conn=None):
-        self.console = console
-        self.dom1  = int(dom1)
-        self.port1 = int(port1)
-        self.dom2  = int(dom2)
-        self.port2 = int(port2)
-        self.conn  = conn
-        #self.id = "%d.%d-%d.%d" % (self.dom1, self.port1, self.dom2, self.port2)
-        self.id = str(port1)
-
-    def __str__(self):
-        s = "console"
-        s += " id=%s" % self.id
-        s += " src=%d.%d" % (self.dom1, self.port1)
-        s += " dst=%d.%d" % (self.dom2, self.port2)
-        s += " port=%s" % self.console
-        if self.conn:
-            s += " conn=%s:%s" % (self.conn[0], self.conn[1])
-        return s
-
-    def sxpr(self):
-        sxpr = ['console',
-                ['id', self.id],
-                ['src', self.dom1, self.port1],
-                ['dst', self.dom2, self.port2],
-                ['port', self.console],
-                ]
-        if self.conn:
-            sxpr.append(['connected', self.conn[0], self.conn[1]])
-        return sxpr
-
-    def connection(self):
-        return self.conn
-
-    def update(self, consinfo):
-        conn = sxp.child(consinfo, 'connected')
-        if conn:
-            self.conn = conn[1:]
-        else:
-            self.conn = None
-
-    def uri(self):
-        """Get the uri to use to connect to the console.
-        This will be a telnet: uri.
-
-        return uri
-        """
-        host = socket.gethostname()
-        return "telnet://%s:%s" % (host, self.console)
-
 class XendConsole:
 
-    dbpath = "console"
-
     def  __init__(self):
-        self.db = XendDB.XendDB(self.dbpath)
-        self.console = {}
-        self.console_db = self.db.fetchall("")
-        if xroot.get_rebooted():
-            print 'XendConsole> rebooted: removing all console info'
-            self.rm_all()
+        pass
         eserver.subscribe('xend.domain.died', self.onDomainDied)
         eserver.subscribe('xend.domain.destroy', self.onDomainDied)
 
-    def rm_all(self):
-        """Remove all console info. Used after reboot.
-        """
-        for (k, v) in self.console_db.items():
-            self._delete_console(k)
-
-    def refresh(self):
-        consoles = daemon.consoles()
-        cons = {}
-        for consinfo in consoles:
-            id = str(sxp.child_value(consinfo, 'id'))
-            cons[id] = consinfo
-            if id not in self.console:
-                self._new_console(consinfo)
-        for c in self.console.values():
-            consinfo = cons.get(c.id)
-            if consinfo:
-                c.update(consinfo)
-            else:
-                self._delete_console(c.id)
-
     def onDomainDied(self, event, val):
-        dom = int(val)
-        #print 'XendConsole>onDomainDied', 'event', event, "dom=", dom
-        for c in self.consoles():
-            #print 'onDomainDied', "dom=", dom, "dom1=", c.dom1, "dom2=", c.dom2
-            if (c.dom1 == dom) or (c.dom2 == dom):
-                'XendConsole>onDomainDied', 'delete console dom=', dom
-                ctrl = daemon.get_domain_console(dom)
-                if ctrl:
-                    ctrl.close()
-                self._delete_console(c.id)
-
-    def sync(self):
-        self.db.saveall("", self.console_db)
-
-    def sync_console(self, id):
-        self.db.save(id, self.console_db[id])
-
-    def _new_console(self, consinfo):
-        # todo: xen needs a call to get current domain id.
-        dom1 = 0
-        port1 = sxp.child_value(consinfo, 'local_port')
-        dom2 = sxp.child_value(consinfo, 'domain')
-        port2 = sxp.child_value(consinfo, 'remote_port')
-        console = sxp.child_value(consinfo, 'console_port')
-        info = XendConsoleInfo(console, dom1, int(port1), int(dom2), int(port2))
-        info.update(consinfo)
-        self._add_console(info.id, info)
-        return info
-
-    def _add_console(self, id, info):
-        self.console[id] = info
-        self.console_db[id] = info.sxpr()
-        self.sync_console(id)
-
-    def _delete_console(self, id):
-        if id in self.console:
-            del self.console[id]
-        if id in self.console_db:
-            del self.console_db[id]
-            self.db.delete(id)
+        pass
 
     def console_ls(self):
-        self.refresh()
-        return self.console.keys()
+        return [ c.console_port for c in self.consoles() ]
 
     def consoles(self):
-        self.refresh()
-        return self.console.values()
+        return daemon.get_consoles()
     
     def console_create(self, dom, console_port=None):
         consinfo = daemon.console_create(dom, console_port=console_port)
-        info = self._new_console(consinfo)
-        return info
+        return consinfo
     
     def console_get(self, id):
-        self.refresh()
-        return self.console.get(id)
-
-    def console_delete(self, id):
-        self._delete_console(id)
+        id = int(id)
+        for c in self.consoles():
+            if c.console_port == id:
+                return c
+        return None
 
     def console_disconnect(self, id):
-        id = int(id)
-        daemon.console_disconnect(id)
+        console = self.console_get(id)
+        if not console:
+            raise XendError('Invalid console id')
+        console.disconnect()
 
 def instance():
     global inst
index d064307a3a264774bc446064a1d9951e865816ac..f44b5c1ad5a820833052f84447f1f569dd4c3af6 100644 (file)
@@ -18,7 +18,6 @@ import XendRoot
 xroot = XendRoot.instance()
 import XendDB
 import XendDomainInfo
-import XendConsole
 import XendMigrate
 import EventServer
 from XendError import XendError
@@ -42,14 +41,13 @@ class XendDomain:
     """Table of domain info indexed by domain id."""
     domain = {}
     
-    """Table of configs for domain restart, indexed by domain id."""
+    """Table of domains to restart, indexed by domain id."""
     restarts = {}
 
     """Table of delayed calls."""
     schedule = {}
     
     def __init__(self):
-        self.xconsole = XendConsole.instance()
         # Table of domain info indexed by domain id.
         self.db = XendDB.XendDB(self.dbpath)
         self.domain_db = self.db.fetchall("")
@@ -322,6 +320,19 @@ class XendDomain:
         deferred.addCallback(fn)
         return deferred
 
+    def domain_restart(self, dominfo):
+        """Restart a domain.
+
+        @param dominfo: domain object
+        @return: deferred
+        """
+        deferred = dominfo.restart()
+        def fn(dominfo):
+            self._add_domain(dominfo.id, dominfo)
+            return dominfo
+        deferred.addCallback(fn)
+        return deferred        
+
     def domain_configure(self, id, config):
         """Configure an existing domain. This is intended for internal
         use by domain restore and migrate.
@@ -405,7 +416,7 @@ class XendDomain:
         if reason == 'halt':
             self.domain_restart_cancel(id)
         else:
-            self.domain_restart_schedule(id, reason, set=1)
+            self.domain_restart_schedule(id, reason, force=1)
         eserver.inject('xend.domain.shutdown', [id, reason])
         if reason == 'halt':
             reason = 'poweroff'
@@ -413,25 +424,22 @@ class XendDomain:
         self.refresh_schedule()
         return val
 
-    def domain_restart_schedule(self, id, reason, set=0):
+    def domain_restart_schedule(self, id, reason, force=0):
         """Schedule a restart for a domain if it needs one.
 
         @param id:     domain id
         @param reason: shutdown reason
         """
-        log.debug('domain_restart_schedule> %s %s %d', id, reason, set)
+        log.debug('domain_restart_schedule> %s %s %d', id, reason, force)
         dominfo = self.domain.get(id)
         if not dominfo:
             return
         if id in self.restarts:
             return
-        if set and reason == 'reboot':
-            dominfo.restart_mode = XendDomainInfo.RESTART_ALWAYS
-        restart = dominfo.restart_needed(reason)
+        restart = (force and reason == 'reboot') or dominfo.restart_needed(reason)
         if restart:
-            # Avoid multiple restarts.
-            dominfo.restart_mode = XendDomainInfo.RESTART_NEVER
-            self.restarts[id] = dominfo.config
+            dominfo.restarting()
+            self.restarts[id] = dominfo
             log.info('Scheduling restart for domain: id=%s name=%s', id, dominfo.name)
             self.domain_restarts_schedule()
             
@@ -440,10 +448,9 @@ class XendDomain:
 
         @param id: domain id
         """
-        dominfo = self.domain.get(id)
+        dominfo = self.restarts.get(id)
         if dominfo:
-            dominfo.restart_mode = XendDomainInfo.RESTART_NEVER
-        if id in self.restarts:
+            dominfo.restart_cancel()
             del self.restarts[id]
 
     def domain_restarts(self):
@@ -454,17 +461,17 @@ class XendDomain:
             if id in self.domain:
                 # Don't execute restart for domains still running.
                 continue
-            config = self.restarts[id]
+            dominfo = self.restarts[id]
             # Remove it from the restarts.
             del self.restarts[id]
             try:
-                log.info('domain_restarts> restart: id=%s config=%s', id, str(config))
+                log.info('domain_restarts> restart: id=%s config=%s', id, str(dominfo.config))
                 def cbok(dominfo):
                     log.info('Restarted domain %s as %s', id, dominfo.id)
                     self.domain_unpause(dominfo.id)
                 def cberr(err):
                     log.exception("Delayed exception restarting domain")
-                deferred = self.domain_create(config)
+                deferred = self.domain_restart(dominfo)
                 deferred.addCallback(cbok)
                 deferred.addErrback(cberr)
             except:
@@ -500,7 +507,7 @@ class XendDomain:
         if reason == 'halt':
             self.domain_restart_cancel(id)
         elif reason == 'reboot':
-            self.domain_restart_schedule(id, reason, set=1)
+            self.domain_restart_schedule(id, reason, force=1)
         val = self.final_domain_destroy(id)
         self.refresh_schedule()
         return val
index a519645c1aea173f08f2aceb6fd3f95eee011dea..2a2743d3ef2bbf22ccb5034df9ac84ba95b52699 100644 (file)
@@ -62,6 +62,9 @@ restart_modes = [
     RESTART_NEVER,
     ]
 
+STATE_RESTART_PENDING = 'pending'
+STATE_RESTART_BOOTING = 'booting'
+
 def shutdown_reason(code):
     """Get a shutdown reason from a code.
 
@@ -277,6 +280,7 @@ def vm_recreate(savedinfo, info):
     else:
         d = defer.Deferred()
         d.callback(vm)
+    vm.recreate = 0
     return d
 
 def vm_restore(src, progress=0):
@@ -360,8 +364,8 @@ class XendDomainInfo:
         self.state = self.STATE_OK
         #todo: set to migrate info if migrating
         self.migrate = None
-        #Whether to auto-restart
         self.restart_mode = RESTART_ONREBOOT
+        self.restart_state = None
         self.console_port = None
 
     def setdom(self, dom):
@@ -380,7 +384,7 @@ class XendDomainInfo:
         s += " name=" + self.name
         s += " memory=" + str(self.memory)
         if self.console:
-            s += " console=" + self.console.id
+            s += " console=" + str(self.console.console_port)
         if self.image:
             s += " image=" + self.image
         s += ""
@@ -536,6 +540,8 @@ class XendDomainInfo:
         devices have been released.
         """
         if self.dom is None: return 0
+        if self.restart_state == STATE_RESTART_PENDING and self.console:
+            self.console.deregisterChannel()
         chan = xend.getDomChannel(self.dom)
         if chan:
             log.debug("Closing channel to domain %d", self.dom)
@@ -603,7 +609,8 @@ class XendDomainInfo:
         memory = self.memory
         name = self.name
         cpu = int(sxp.child_value(self.config, 'cpu', '-1'))
-        dom = xc.domain_create(mem_kb= memory * 1024, name= name, cpu= cpu)
+        dom = self.dom or 0
+        dom = xc.domain_create(dom= dom, mem_kb= memory * 1024, name= name, cpu= cpu)
         if dom <= 0:
             raise VmError('Creating domain failed: name=%s memory=%d'
                           % (name, memory))
@@ -627,7 +634,7 @@ class XendDomainInfo:
         if self.blkif_backend: flags |= SIF_BLK_BE_DOMAIN
         err = buildfn(dom            = dom,
                       image          = kernel,
-                      control_evtchn = self.console.port2,
+                      control_evtchn = self.console.getRemotePort(),
                       cmdline        = cmdline,
                       ramdisk        = ramdisk,
                       flags          = flags)
@@ -650,7 +657,10 @@ class XendDomainInfo:
             if ramdisk and not os.path.isfile(ramdisk):
                 raise VmError('Kernel ramdisk does not exist: %s' % ramdisk)
         self.init_domain()
-        self.console = xendConsole.console_create(self.dom, console_port=self.console_port)
+        if self.console:
+            self.console.registerChannel()
+        else:
+            self.console = xendConsole.console_create(self.dom, console_port=self.console_port)
         self.build_domain(ostype, kernel, ramdisk, cmdline, vifs_n)
         self.image = kernel
         self.ramdisk = ramdisk
@@ -737,6 +747,20 @@ class XendDomainInfo:
             return reason == 'reboot'
         return 0
 
+    def restart_cancel(self):
+        self.restart_state = None
+
+    def restarting(self):
+        self.restart_state = STATE_RESTART_PENDING
+
+    def restart(self):
+        try:
+            self.restart_state = STATE_RESTART_BOOTING
+            d = self.construct(self.config)
+        finally:
+            self.restart_state = None
+        return d
+
     def configure_backends(self):
         """Set configuration flags if the vm is a backend for netif of blkif.
         """
index eb8994425b998fedfb1c5f8658ccb9c104a3809a..4105fa6e16777ec6e93473d943ff62e70477ccec 100644 (file)
@@ -14,7 +14,7 @@ class SrvConsole(SrvDir):
         self.xc = XendConsole.instance()
 
     def op_disconnect(self, op, req):
-        val = self.xc.console_disconnect(self.info.id)
+        val = self.xc.console_disconnect(self.info.console_port)
         return val
 
     def render_POST(self, req):
@@ -31,7 +31,7 @@ class SrvConsole(SrvDir):
                 #self.ls()
                 req.write('<p>%s</p>' % self.info)
                 req.write('<p><a href="%s">Connect to domain %d</a></p>'
-                          % (self.info.uri(), self.info.dom2))
+                          % (self.info.uri(), self.info.dom))
                 self.form(req)
                 req.write('</body></html>')
             return ''
@@ -40,6 +40,6 @@ class SrvConsole(SrvDir):
 
     def form(self, req):
         req.write('<form method="post" action="%s">' % req.prePathURL())
-        if self.info.connection():
+        if self.info.connected():
             req.write('<input type="submit" name="op" value="disconnect">')
         req.write('</form>')
index 1371f6c0ef2f558fa33075f903e6f0b04e38b87f..139f3025368c0f5bbd987fde92c97a4ae468ff4b 100644 (file)
@@ -55,8 +55,9 @@ class SrvConsoleDir(SrvDir):
             sxp.show(consoles, out=req)
         else:
             consoles = self.xconsole.consoles()
-            consoles.sort(lambda x, y: cmp(x.id, y.id))
+            consoles.sort(lambda x, y: cmp(x.console_port, y.console_port))
             req.write('<ul>')
             for c in consoles:
-                req.write('<li><a href="%s%s"> %s</a></li>' % (url, c.id, c))
+                cid = str(c.console_port)
+                req.write('<li><a href="%s%s"> %s</a></li>' % (url, cid, cid))
             req.write('</ul>')
index 1102baae1dc1e5f85f964156f7aed57e36c131c1..aaeef6d54cfcf5e2d920af3a322a63bb8283929b 100644 (file)
@@ -121,7 +121,7 @@ class MgmtProtocol(protocol.DatagramProtocol):
         console_port = sxp.child_value(req, 'console_port')
         if console_port:
             console_port = int(console_port)
-        resp = self.daemon.console_create(dom, console_port)
+        resp = self.daemon.console_create(dom, console_port).sxpr()
         print name, resp
         return resp
 
@@ -730,11 +730,14 @@ class Daemon:
         console = self.consoleCF.getInstanceByDom(dom)
         if console is None:
             console = self.consoleCF.createInstance(dom, console_port)
-        return console.sxpr()
+        return console
 
     def consoles(self):
         return [ c.sxpr() for c in self.consoleCF.getInstances() ]
 
+    def get_consoles(self):
+        return self.consoleCF.getInstances()
+
     def get_console(self, id):
         return self.consoleCF.getInstance(id)
 
index eed90b7feb5fd2e19cb6f96514e0cac4fad5ca08..8eb5e127197494092981d64d8d012082759ee261 100644 (file)
@@ -204,7 +204,7 @@ class SrvDomain(SrvDir):
             req.write('<p>%s</p>' % self.dom)
             if self.dom.console:
                 cinfo = self.dom.console
-                cid = cinfo.id
+                cid = str(cinfo.console_port)
                 #todo: Local xref: need to know server prefix.
                 req.write('<p><a href="/xend/console/%s">Console %s</a></p>'
                           % (cid, cid))
index 3c6325f8af86fb1a12d10392ecf4a8e3efba3efe..b3231beb1283a8737bc526f2221721ef5b2ddeeb 100755 (executable)
@@ -1,5 +1,7 @@
 # Copyright (C) 2004 Mike Wray <mike.wray@hp.com>
 
+import socket
+
 from twisted.internet import reactor
 from twisted.internet import protocol
 
@@ -124,17 +126,38 @@ class ConsoleController(controller.Controller):
         self.listen()
 
     def sxpr(self):
-        val =['console',
-              ['status',       self.status ],
-              ['id',           self.idx ],
-              ['domain',       self.dom ],
-              ['local_port',   self.channel.getLocalPort() ],
-              ['remote_port',  self.channel.getRemotePort() ],
-              ['console_port', self.console_port ] ]
+        val = ['console',
+               ['status', self.status ],
+               ['id',     self.idx    ],
+               ['domain', self.dom    ] ]
+        val.append(['local_port',   self.getLocalPort()  ])
+        val.append(['remote_port',  self.getRemotePort() ])
+        val.append(['console_port', self.console_port    ])
         if self.addr:
             val.append(['connected', self.addr[0], self.addr[1]])
         return val
 
+    def getLocalPort(self):
+        if self.channel:
+            return self.channel.getLocalPort()
+        else:
+            return 0
+
+    def getRemotePort(self):
+        if self.channel:
+            return self.channel.getRemotePort()
+        else:
+            return 0
+
+    def uri(self):
+        """Get the uri to use to connect to the console.
+        This will be a telnet: uri.
+
+        return uri
+        """
+        host = socket.gethostname()
+        return "telnet://%s:%d" % (host, self.console_port)
+
     def ready(self):
         return not (self.closed() or self.rbuf.empty())
 
@@ -222,7 +245,7 @@ class ConsoleController(controller.Controller):
         Writes as much to the channel as it can.
         """
         work = 0
-        while not self.wbuf.empty() and self.channel.writeReady():
+        while self.channel and not self.wbuf.empty() and self.channel.writeReady():
             msg = xu.message(CMSG_CONSOLE, 0, 0)
             msg.append_payload(self.wbuf.read(msg.MAX_PAYLOAD))
             work += self.channel.writeRequest(msg, notify=0)
@@ -240,7 +263,7 @@ class ConsoleController(controller.Controller):
         if self.closed(): return -1
         if conn != self.conn: return 0
         self.wbuf.write(data)
-        if self.produceRequests():
+        if self.channel and self.produceRequests():
             self.channel.notify()
         return 0
 
index e63585b212bd7b2624e5140685d78b088f21e454..756a7cd64e03434bb68ba65cc4b5d72c235b04db 100755 (executable)
@@ -199,7 +199,7 @@ class CtrlMsgRcvr:
         """
         if self.channel:
             self.channel.deregisterDevice(self)
-            del self.channel
+            self.channel = None
 
     def produceRequests(self):
         """Produce any queued requests.
index ce56b75d9ca940e302f89159468e96298b95e01b..3b7982f5e52c315f42980df7bf8c3071805bdb97 100644 (file)
@@ -405,7 +405,7 @@ def make_domain(opts, config):
     dom = int(sxp.child_value(dominfo, 'id'))
     console_info = sxp.child(dominfo, 'console')
     if console_info:
-        console_port = int(sxp.child_value(console_info, 'port'))
+        console_port = int(sxp.child_value(console_info, 'console_port'))
     else:
         console_port = None